package zone

import (
	"context"
	"fmt"
	"github.com/Unixeno/TheMoon/moon/zone/network"
	zf "github.com/Unixeno/TheMoon/zonefile"
	"github.com/Unixeno/islands/pb"
	"net"
	"sync/atomic"
	"unicode"
)

const (
	maxNetworkNameLength = 15
)

type deployer struct {
	zoneID string
	// deployer 版本 与 zonefile 中的版本一致
	version int32

	// containers 是 Zonefile 的容器名字与 Zone 的 Container 名字的映射
	// volumes 与 networks 也是一样
	// 只记录 Create 的 containers volumes networks
	// 保证 Volume 与 Network 的创建一定是在容器创建之前
	// 其中 如果 Zonefile 中 Volume 的名字是大写那么就是公有卷，默认持久化
	// 如果 Zonefile 中 Volume 的名字的开头是 % 表示持久化
	// moon 重启以后会从本地数据库中拿到之前数据
	containers map[string]string
	volumes    map[string]string
	networks   map[string]string

	spinlock      *int32
	islandsClient pb.IslandsClient
}

func (d *deployer) CreateContainer(ctx context.Context, containerConfig *zf.Container) (string, error) {

	// 镜像引用计数 +1
	imageId, err := d.checkOrPullImage(ctx, containerConfig.Image)
	if err != nil {
		return "", err
	}
	counter := collector.loadOrCreate(imageId, imageTypeCounter)
	counter.add()

	id, err := d.createContainer(ctx, imageId, containerConfig)
	if err != nil {
		return "", err
	}

	// 创建成功
	d.containers[containerConfig.Name] = d.getPrefix() + containerConfig.Name

	d.save()

	// 网络引用计数 +1
	if containerConfig.Interface != nil {
		for _, i := range containerConfig.Interface {
			s := i.Network
			name, ok := d.networks[s]
			if !ok {
				continue
			}
			counter = collector.loadOrCreate(name, networkTypeCounter)
			counter.add()

			// 公有网络引用计数多 +1
			if d.isPublic(name) {
				counter.add()
			}
		}
	}

	// 卷引用计数 +1
	for name, _ := range containerConfig.Volumes {
		counter := collector.loadOrCreate(d.volumes[name], volumeTypeCounter)
		counter.add()

		// 如果是公有卷和持久化卷引用计数多加 1
		if d.isPersistent(name) {
			counter.add()
		}
	}

	return id, nil
}

func (d *deployer) CreateVolume(ctx context.Context, volumeConfig *zf.Volume) error {
	logger.WithField("name", volumeConfig.Name).Debug("Create Volume...")
	name := d.getVolumeRealName(volumeConfig.Name)

	path, ok := d.getVolumeHostPath(ctx, name)
	if ok {
		if volumeConfig.Source != "" && path != volumeConfig.Source {
			return fmt.Errorf("there is a conflict between %v and %v", path, volumeConfig.Source)
		}
		d.volumes[volumeConfig.Name] = name
		return nil
	}
	// 建立映射
	d.volumes[volumeConfig.Name] = name
	d.save()
	return d.createVolume(ctx, volumeConfig)
}

func (d *deployer) RemoveVolume(ctx context.Context, volumeConfig *zf.Volume) error {
	name := d.getPrefix() + volumeConfig.Name
	if !d.isVolumeExist(ctx, name) {
		return zf.ErrVolumeNotExist
	}

	return d.removeVolume(ctx, name)
}

func (d *deployer) RunContainer(ctx context.Context, containerConfig *zf.Container) error {
	logger.WithField("name", containerConfig.Name).Debug("run container")
	id, err := d.CreateContainer(ctx, containerConfig)
	if err != nil {
		return err
	}
	if err := d.startContainer(ctx, id); err != nil {
		logger.WithError(err).Debug("failed to start a container")
		_ = d.deleteContainer(ctx, id)
		return err
	}
	return nil
}

func (d *deployer) WaitContainer(ctx context.Context, containerConfig *zf.Container) error {
	id, err := d.getContainerId(ctx, d.getPrefix()+containerConfig.Name)
	if err != nil {
		return err
	}
	return d.waitContainer(ctx, id)
}

func (d *deployer) deleteContainer(ctx context.Context, id string) error {
	logger.Debug("deleteContainer ", id)
	_ = d.stopContainer(ctx, id)

	info, err := d.getContainerInfo(ctx, id)
	if err != nil {
		return err
	}
	logger.Debug("Release all ip...")
	// 释放所有 ip
	for _, i := range info.Network.Interfaces {
		bridgeNetwork := network.GetNetwork(i.Bridge)
		if bridgeNetwork == nil {
			continue
		}
		for _, a := range i.Address {
			ip := net.ParseIP(a.Ip)
			bridgeNetwork.Release(ip)
		}
	}
	if err := d.removeContainer(ctx, id); err != nil {
		return err
	}
	return nil
}

func (d *deployer) DeleteContainer(ctx context.Context, containerConfig *zf.Container) error {
	logger.WithField("name", containerConfig.Name).Debug("delete container")
	id, err := d.getContainerId(ctx, d.getPrefix()+containerConfig.Name)
	if err != nil && err != errContainerNotExist {
		return err
	}
	if err == errContainerNotExist {
		return zf.ErrContainerNotExist
	}
	if err := d.deleteContainer(ctx, id); err != nil {
		return err
	}

	delete(d.containers, d.getPrefix()+containerConfig.Name)
	d.save()
	// 镜像引用 -1
	imageId, err := d.getImageId(ctx, containerConfig.Image)
	if err != errImageNotExist {
		return err
	}

	if counter, ok := collector.get(imageId); ok {
		counter.sub()
	}

	// 卷引用 -1
	for name, _ := range containerConfig.Volumes {
		if counter, ok := collector.get(d.volumes[name]); ok {
			counter.sub()
		}
	}

	// 网络引用 -1
	if containerConfig.Interface != nil {
		for _, i := range containerConfig.Interface {
			s := i.Network
			name, ok := d.networks[s]
			if !ok {
				continue
			}
			if counter, ok := collector.get(name); ok {
				counter.sub()
			}
		}

	}

	return nil
}

func (d *deployer) EnableNetwork(ctx context.Context, networkConfig *zf.Network) error {
	logger.WithField("name", networkConfig.Name).WithField("cmd", "enable").Debug("handle network")
	bridgeName := d.getBridgeName(networkConfig.Name)
	ip, ipNet, err := net.ParseCIDR(networkConfig.Address)
	if err != nil {
		logger.WithError(err).Error()
		return err
	}

	var opt = &network.BridgeNetworkOptions{
		Addr: &net.IPNet{
			IP:   ip,
			Mask: ipNet.Mask,
		},
	}

	if len(networkConfig.BridgeTo) == 0 {
		opt.Method = network.BridgeMethodNat
	} else {
		opt.Method = network.BridgeMethodBridge
		opt.BridgeTo = networkConfig.BridgeTo
	}
	d.networks[networkConfig.Name] = bridgeName

	d.save()

	_, err = network.CreateNetwork("bridge", bridgeName, opt)
	if err != nil {
		return err
	}
	return nil
}

func (d *deployer) DisableNetwork(ctx context.Context, networkConfig *zf.Network) error {
	logger.WithField("name", networkConfig.Name).WithField("cmd", "disable").Debug("handle network")
	bridgeName := d.getBridgeName(networkConfig.Name)
	return d.removeNetwork(ctx, bridgeName)
}

func (d *deployer) removeNetwork(ctx context.Context, name string) error {
	for sourceName, currentName := range d.networks {
		if currentName == name {
			if err := network.DeleteNetwork(name); err != nil {
				return err
			}
			delete(d.networks, sourceName)
			d.save()
		}
	}
	return nil
}

func (d *deployer) clean(ctx context.Context, zonefile *zf.Zonefile) error {
	logger.WithField("zoneID", d.zoneID).Debug("cleaning...")
	// 执行 zonefile 的 cleanTask
	if err := zonefile.ExecuteCleanTask(ctx, d); err != nil {
		return err
	}
	// 回收所有的 container
	logger.Debug("clean container")
	for _, name := range d.containers {
		id, err := d.getContainerId(ctx, name)
		if err != nil && err != errContainerNotExist {
			return err
		}
		if err == errContainerNotExist {
			continue
		}
		if err := d.deleteContainer(ctx, id); err != nil {
			return err
		}
	}

	// 将镜像卷网络引用计数 -1
	collector.Range(func(key interface{}, value interface{}) bool {
		c := value.(*counter)
		if c.zoneID == d.zoneID {
			c.sub()
		}
		return true
	})

	// 清空 container 的 map
	d.containers = make(map[string]string)

	return nil
}

// 如果是卷的话公有卷默认持久化
func (d *deployer) isPublic(name string) bool {
	return name[0] != '_'
}

// 持久化是对卷来说的
// 如果是公有卷默认持久化
// 如果是私有卷第二个字母大写就是持久化的
func (d *deployer) isPersistent(name string) bool {
	if name[0] == '_' {
		if len(name) >= 2 && unicode.IsUpper(rune(name[1])) {
			return true
		}
		return false
	}
	return unicode.IsUpper(rune(name[0]))
}

func (d *deployer) getPrefix() string {
	return d.zoneID + "_"
}

func (d *deployer) getVolumeRealName(name string) string {
	// 公有卷
	if d.isPublic(name) {
		return name
	}
	// 可持久化卷和私有卷都要加前缀
	return d.getPrefix() + name
}

func (d *deployer) lock() {
	atomic.StoreInt32(d.spinlock, 1)
}

func (d *deployer) unlock() {
	atomic.StoreInt32(d.spinlock, 0)
}

func (d *deployer) tryLock() bool {
	return atomic.SwapInt32(d.spinlock, 1) == 0
}

func (d *deployer) getBridgeName(name string) string {
	// 公有网络
	if d.isPublic(name) {
		return name
	}
	// 私有网络
	l := maxNetworkNameLength - len(name)
	prefix := d.getPrefix()
	if l > len(d.zoneID) {
		return prefix + name
	}
	return prefix[:l] + name
}

func (d *deployer) save() {
	saveDeployer(d)
}

func (d *deployer) delete() {
	deleteDeployer(d)
}
